package com.fakepath.helpers;

import java.util.LinkedList;
import java.util.List;

import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import android.content.Context;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.view.animation.TranslateAnimation;
import android.widget.AbsListView;
import android.widget.TextView;

import com.example.pathfake.Direction;
import com.example.pathfake.R;
import com.fakepath.animations.InOutAnimation;
import com.fakepath.listeners.OnScrollBarListener;
import com.fakepath.utils.ViewUtils;

public abstract class Clock implements OnScrollBarListener {
	private static final float ANGLE_PER_HOUR = 30.0F;
	private static final float ANGLE_PER_HOUR_PER_MINUTE = 0.5F;
	private static final float ANGLE_PER_MINUTE = 6.0F;
	private static final float ROTATION_DURATION_HOUR = 1000.0F;
	private static final float ROTATION_DURATION_MINUTE = 333.33334F;

	private static Interpolator clockInterpolator;
	private static DateTimeFormatter dayOfWeekFormat;
	private static DateTimeFormatter dayOfYearFormat;
	private static DateTimeFormatter fullDateFormat;
	private DateTimeFormatter timeFormat = DateTimeFormat.shortTime();
	private ClockAnimation animIn;
	private ClockAnimation animOut;
	private Context applicationContext;
	private int[] coords = new int[2];
	private DateTime dateTime;
	private TextView digitalDate;
	private TextView digitalTime;
	private Animation.AnimationListener hourAnimationListener;
	private List<Animation> hourAnimationQueue = new LinkedList<Animation>();

	private View hourHand;
	private volatile boolean isHourAnimating;
	private volatile boolean isMinuteAnimating;
	private volatile boolean isViewAnimatingIn;
	private volatile boolean isViewAnimatingOut;
	private volatile boolean isVisible;
	private Animation.AnimationListener minuteAnimationListener;
	private List<Animation> minuteAnimationQueue = new LinkedList<Animation>();
	private Object minuteAnimationQueueLock = new Object();
	private Object hourAnimationQueueLock = new Object();
	private View minuteHand;
	private DateTime now;
	private View rootView;
	private boolean isFling;

	protected abstract Handler getHandler();

	static {
		dayOfWeekFormat = DateTimeFormat.forPattern("EEEE");
		dayOfYearFormat = DateTimeFormat.forPattern("MMMM d");
		fullDateFormat = DateTimeFormat.forPattern("MMMM d, YYYY");
		clockInterpolator = new LinearInterpolator();
	}

	private final Runnable startClockHandAnimations = new Runnable() {
		public void run() {

			synchronized (Clock.this.hourAnimationQueueLock) {
				if ((!Clock.this.hourAnimationQueue.isEmpty())
						&& (!Clock.this.isHourAnimating)) {
					Clock.this.hourHand
							.startAnimation((Animation) Clock.this.hourAnimationQueue
									.remove(0));
				}
			}
			synchronized (Clock.this.minuteAnimationQueueLock) {
				if ((!Clock.this.minuteAnimationQueue.isEmpty())
						&& (!Clock.this.isMinuteAnimating)) {
					Clock.this.minuteHand
							.startAnimation((Animation) Clock.this.minuteAnimationQueue
									.remove(0));
				}
			}
		}
	};

	private View target;
	private Runnable tryToHide = new Runnable() {
		public void run() {
			if (Clock.this.isVisible) {
				if (!Clock.this.isAnimating()) {
					Clock.this.rootView.startAnimation(Clock.this.animOut);
				}
			}
		}
	};

	private Runnable terminateHide = new Runnable() {
		public void run() {
			Clock.this.rootView.startAnimation(Clock.this.animOut);
		}
	};

	private final Runnable tryToShow = new Runnable() {
		public void run() {
			if (Clock.this.isViewAnimatingOut) {
				Handler localHandler = Clock.this.getHandler();
				localHandler.removeCallbacks(this);
				localHandler.post(this);
			} else {
				if (!Clock.this.isVisible) {
					Clock.this.rootView.startAnimation(Clock.this.animIn);
				}
			}
		}
	};
	private boolean isScrolling;
	private float lastDegreesMinutue = 0.0F;
	private float lastDegreeHours = 0.0F;

	public boolean isAnimating() {
		if ((this.isHourAnimating) || (this.isMinuteAnimating)
				|| (this.isViewAnimatingIn) || (this.isViewAnimatingOut)) {
			return true;
		} else {
			return false;
		}
	}

	private static class ClockAnimation extends InOutAnimation {
		private final int translateAmount;

		public ClockAnimation(Direction paramDirection, View paramView,
				Animation.AnimationListener paramAnimationListener) {
			super(paramDirection, 1000L, new View[0]);
			this.translateAmount = ViewUtils.dipToPx(paramView.getContext()
					.getApplicationContext(), 24.0F);
			setAnimationListener(paramAnimationListener);
		}

		public void addInAnimation(View[] paramArrayOfView) {
			addAnimation(new AlphaAnimation(0.0F, 1.0F));
			addAnimation(new TranslateAnimation(this.translateAmount, 0.0F,
					0.0F, 0.0F));
		}

		public void addOutAnimation(View[] paramArrayOfView) {
			addAnimation(new AlphaAnimation(1.0F, 0.0F));
			addAnimation(new TranslateAnimation(0.0F, this.translateAmount,
					0.0F, 0.0F));
		}
	}

	public Clock(View paramView) {
		this.applicationContext = paramView.getContext()
				.getApplicationContext();
		this.rootView = paramView;
		this.isFling = false;
		this.hourHand = this.rootView.findViewById(R.id.clock_face_hour);
		this.minuteHand = this.rootView.findViewById(R.id.clock_face_minute);
		this.digitalTime = ((TextView) this.rootView
				.findViewById(R.id.clock_digital_time));
		this.digitalDate = ((TextView) this.rootView
				.findViewById(R.id.clock_digital_date));
		this.target = this.rootView.findViewById(R.id.clock_target);
		this.now = getNow();
		this.dateTime = null;

		this.animIn = new ClockAnimation(Direction.IN, this.rootView,
				new MyAnimationListener() {
					public void onAnimationEnd() {
						isViewAnimatingIn = false;
					}

					void onAnimationStart() {
						isViewAnimatingIn = true;
						isVisible = true;
					}
				});

		this.animOut = new ClockAnimation(Direction.OUT, this.rootView,
				new MyAnimationListener() {
					public void onAnimationEnd() {
						isViewAnimatingOut = false;
						isVisible = false;
					}

					void onAnimationStart() {
						isViewAnimatingOut = true;
					}
				});

		this.hourAnimationListener = new MyAnimationListener() {
			public void onAnimationEnd() {
				synchronized (Clock.this.hourAnimationQueueLock) {
					isHourAnimating = false;
					if (Clock.this.hourAnimationQueue.isEmpty() && !isFling
							&& !isScrolling) {
						hide();
						// Clock.this.hideIfWaiting();
						return;
					} else if (!Clock.this.hourAnimationQueue.isEmpty()) {
						Clock.this.hourHand
								.startAnimation((Animation) Clock.this.hourAnimationQueue
										.remove(0));
					}
				}
			}

			void onAnimationStart() {
				isHourAnimating = true;
			}
		};
		this.minuteAnimationListener = new MyAnimationListener() {
			public void onAnimationEnd() {
				synchronized (Clock.this.minuteAnimationQueueLock) {
					isMinuteAnimating = false;
					if (Clock.this.minuteAnimationQueue.isEmpty() && !isFling
							&& !isScrolling) {
						hide();
						return;
					} else if (!Clock.this.minuteAnimationQueue.isEmpty()) {
						Clock.this.minuteHand
								.startAnimation((Animation) Clock.this.minuteAnimationQueue
										.remove(0));
					}
				}
			}

			void onAnimationStart() {
				isMinuteAnimating = true;
			}
		};
	}

	private static float getHourAngle(DateTime paramDateTime) {
		return ANGLE_PER_HOUR * (paramDateTime.getHourOfDay() % 12)
				+ ANGLE_PER_HOUR_PER_MINUTE * paramDateTime.getMinuteOfHour();
	}

	private synchronized Animation getHourAnimation(DateTime paramDateTime1,
			DateTime paramDateTime2) {
		float fromDegrees = lastDegreeHours;
		float f1;
		float toDegrees;
		if (paramDateTime1 == null) {
			f1 = 0.0F;
		} else {
			f1 = getHourAngle(paramDateTime1);
		}
		toDegrees = normalizeNowAngle(f1, getHourAngle(paramDateTime2),
				paramDateTime1, paramDateTime2);
		lastDegreeHours = toDegrees;
		RotateAnimation localRotateAnimation = new RotateAnimation(fromDegrees,
				toDegrees, Animation.RELATIVE_TO_SELF, 0.5F,
				Animation.RELATIVE_TO_SELF, 0.5F);
		localRotateAnimation.setFillAfter(true);
		localRotateAnimation.setFillEnabled(true);
		localRotateAnimation.setDuration(Math.round(ROTATION_DURATION_HOUR
				* Math.abs(toDegrees - f1) / 360.0F));
		localRotateAnimation.setAnimationListener(this.hourAnimationListener);
		localRotateAnimation.setInterpolator(clockInterpolator);
		return localRotateAnimation;
	}

	private synchronized Animation getMinuteAnimation(DateTime paramDateTime1,
			DateTime paramDateTime2) {
		float fromDegrees = lastDegreesMinutue;
		float f1;
		float toDegrees;
		if (paramDateTime1 == null) {
			f1 = 0.0F;
		} else {
			f1 = getMinuteAngle(paramDateTime1);
		}
		toDegrees = normalizeNowAngle(f1, getMinuteAngle(paramDateTime2),
				paramDateTime1, paramDateTime2);
		lastDegreesMinutue = toDegrees;
		RotateAnimation localRotateAnimation = new RotateAnimation(fromDegrees,
				toDegrees, Animation.RELATIVE_TO_SELF, 0.5F,
				Animation.RELATIVE_TO_SELF, 0.5F);
		localRotateAnimation.setFillAfter(true);
		localRotateAnimation.setFillEnabled(true);
		localRotateAnimation.setDuration(Math.round(ROTATION_DURATION_MINUTE
				* Math.abs(toDegrees - f1) / 360.0F));
		localRotateAnimation.setAnimationListener(this.minuteAnimationListener);
		localRotateAnimation.setInterpolator(clockInterpolator);
		return localRotateAnimation;

	}

	private static float getMinuteAngle(DateTime paramDateTime) {
		return ANGLE_PER_MINUTE * paramDateTime.getMinuteOfHour();
	}

	protected abstract DateTime getNow();

	private static abstract class MyAnimationListener implements
			Animation.AnimationListener {
		abstract void onAnimationEnd();

		public final void onAnimationEnd(Animation paramAnimation) {
			onAnimationEnd();
		}

		public final void onAnimationRepeat(Animation paramAnimation) {
		}

		abstract void onAnimationStart();

		public final void onAnimationStart(Animation paramAnimation) {
			onAnimationStart();
		}
	}

	public void hide() {
		Handler localHandler = getHandler();
		localHandler.removeCallbacks(this.tryToHide);
		localHandler.postDelayed(this.tryToHide, 500L);
	}

	public void terminateHide() {
		Handler localHandler = getHandler();
		localHandler.removeCallbacks(this.terminateHide);
		localHandler.postDelayed(this.terminateHide, 50L);
	}

	private static float normalizeNowAngle(float paramFloat1,
			float paramFloat2, DateTime paramDateTime1, DateTime paramDateTime2) {
		float f;
		if (paramDateTime1 == null) {
			f = paramFloat2;
		} else {
			if (paramDateTime2.getMillis() > paramDateTime1.getMillis()) {
				if (paramFloat2 >= paramFloat1) {
					f = paramFloat2;
				}
				f = paramFloat2 + 360.0F;
			}
			if (paramFloat2 > paramFloat1) {
				f = paramFloat2 - 360.0F;
			}
			f = paramFloat2;
		}
		return f;
	}

	private void updateDigitalText(DateTime paramDateTime) {
		ViewGroup.MarginLayoutParams localMarginLayoutParams = (ViewGroup.MarginLayoutParams) this.digitalTime
				.getLayoutParams();
		this.now = getNow();
		int day = this.now.getDayOfYear() - paramDateTime.getDayOfYear();

		if (day == 1) {
			this.digitalDate.setText("Yesterday");
			return;
		}
		//
		if (this.now.getYear() == paramDateTime.getYear()) {
			localMarginLayoutParams.topMargin = ViewUtils.dipToPx(
					this.applicationContext, 7.5F);
			this.digitalDate.setVisibility(ViewGroup.GONE);
			this.digitalTime.setLayoutParams(localMarginLayoutParams);
			this.digitalTime.setText(timeFormat.print(paramDateTime));
			if (day <= 5) {
				this.digitalDate.setText(dayOfWeekFormat.print(paramDateTime));
			} else {
				localMarginLayoutParams.topMargin = this.applicationContext
						.getResources().getDimensionPixelOffset(
								R.dimen.padding_large);
				this.digitalDate.setVisibility(View.VISIBLE);
				this.digitalDate.setText(dayOfYearFormat.print(paramDateTime));
			}
			return;
		}
		//
		//

		this.digitalDate.setText(fullDateFormat.print(paramDateTime));
	}

	public int getTargetLocation() {
		this.target.getLocationOnScreen(this.coords);
		Log.i("Clock", "Target positon = " + this.coords[1]);
		return this.coords[1];
	}

	public boolean isVisible() {
		return this.isVisible;
	}

	public void show() {
		Handler localHandler = getHandler();
		localHandler.removeCallbacks(this.tryToHide);
		localHandler.removeCallbacks(this.tryToShow);
		localHandler.post(this.tryToShow);
	}

	public void update(DateTime pDatetime) {
		if (!pDatetime.equals(this.dateTime)) {
			updateDigitalText(pDatetime);
			this.hourAnimationQueue.add(getHourAnimation(this.dateTime,
					pDatetime));
			this.minuteAnimationQueue.add(getMinuteAnimation(this.dateTime,
					pDatetime));
			getHandler().post(this.startClockHandAnimations);
			this.dateTime = pDatetime;
		}
	}

	@Override
	public void onPositionChange(float offsetY, int type) {
		int heightClock = this.rootView.getHeight();
		ViewGroup.MarginLayoutParams localMarginLayoutParams = (ViewGroup.MarginLayoutParams) this.rootView
				.getLayoutParams();
		localMarginLayoutParams.topMargin = (int) (offsetY - heightClock / 2);
		this.rootView.setLayoutParams(localMarginLayoutParams);
		if (type == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
			onUpdateClock();
			isScrolling = true;
		} else {
			isFling = true;
		}
	}

	public abstract void onUpdateClock();

	@Override
	public void onFringScroll() {
		Log.i("MainActivity", "Fling");
	}

	@Override
	public void onStartScroll() {
		show();
	}

	@Override
	public void onStopScroll() {
		isScrolling = false;
		if (this.isFling) {
			isFling = false;
			onUpdateClock();
		} else {
			hide();
		}
	}
}
